use crate::{
    ast::{types, Expression, Type},
    error::{CompilerError, Result},
    llvm_backend::LLVMBackend,
};
use inkwell::{types::BasicType, values::BasicValueEnum, AddressSpace};

impl<'ctx> LLVMBackend<'ctx> {
    /// Generate extend method for slices: s1.extend(s2)
    /// This method appends all elements from s2 to s1, s2 remains unchanged
    pub(in crate::llvm_backend) fn generate_slice_extend_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // extend() takes exactly one argument: the slice to extend with
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "extend() requires exactly 1 argument (a slice)",
            ));
        }

        // Get the variable pointer and type
        let var_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .clone();

        let vector_type = self
            .variable_type_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
            })?
            .clone();

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type
        } else {
            return Err(CompilerError::internal("extend() requires a slice"));
        };

        // Get the source slice value (s2)
        let source_slice_value = self.generate_expression(&arguments[0])?;

        if !source_slice_value.is_struct_value() {
            return Err(CompilerError::internal("extend() argument must be a slice"));
        }

        let source_slice_struct = source_slice_value.into_struct_value();

        // Extract source slice fields (ptr, len, cap)
        let source_ptr = self
            .builder
            .build_extract_value(source_slice_struct, 0, "source_ptr")?
            .into_pointer_value();
        let source_len = self
            .builder
            .build_extract_value(source_slice_struct, 1, "source_len")?
            .into_int_value();
        let _source_cap = self
            .builder
            .build_extract_value(source_slice_struct, 2, "source_cap")?
            .into_int_value();

        // Load current slice (s1)
        let slice_ptr = var_ptr.into_pointer_value();
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;
        let slice_struct_type = self.context.struct_type(
            &[
                element_llvm_type.ptr_type(AddressSpace::default()).into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;
        let current_slice_struct = current_slice.into_struct_value();

        // Extract current slice fields
        let old_ptr = self
            .builder
            .build_extract_value(current_slice_struct, 0, "old_ptr")?
            .into_pointer_value();
        let old_len = self
            .builder
            .build_extract_value(current_slice_struct, 1, "old_len")?
            .into_int_value();
        let old_cap = self
            .builder
            .build_extract_value(current_slice_struct, 2, "old_cap")?
            .into_int_value();

        // Calculate new length: old_len + source_len
        let new_len = self.builder.build_int_add(old_len, source_len, "new_len")?;

        // Check if we need to reallocate
        let need_realloc = self.builder.build_int_compare(
            inkwell::IntPredicate::UGT,
            new_len,
            old_cap,
            "need_realloc",
        )?;

        // Get element size for memory operations
        let element_size = match &element_type {
            Type::Basic(basic_type) => match basic_type {
                types::BasicType::Int8 | types::BasicType::Uint8 | types::BasicType::Char => 1,
                types::BasicType::Int16 | types::BasicType::Uint16 => 2,
                types::BasicType::Int32 | types::BasicType::Uint32 | types::BasicType::Float32 => 4,
                types::BasicType::Int
                | types::BasicType::Int64
                | types::BasicType::Uint
                | types::BasicType::Uint64
                | types::BasicType::Float64 => 8,
                types::BasicType::Bool => 1,
                _ => 8,
            },
            _ => 8,
        };

        // Create basic blocks for reallocation logic
        let current_func = self
            .current_function
            .ok_or_else(|| CompilerError::internal("No parent function for extend"))?;
        let realloc_block = self
            .context
            .append_basic_block(current_func, "realloc_block");
        let no_realloc_block = self
            .context
            .append_basic_block(current_func, "no_realloc_block");
        let merge_block = self.context.append_basic_block(current_func, "merge_block");

        // Branch based on reallocation need
        self.builder
            .build_conditional_branch(need_realloc, realloc_block, no_realloc_block)?;

        // Realloc block: allocate new memory and copy old data
        self.builder.position_at_end(realloc_block);

        // Calculate new capacity (double or at least new_len)
        let new_cap_doubled = self.builder.build_int_mul(
            old_cap,
            self.context.i64_type().const_int(2, false),
            "new_cap_doubled",
        )?;

        let new_cap_realloc = self
            .builder
            .build_select(
                self.builder.build_int_compare(
                    inkwell::IntPredicate::ULT,
                    new_cap_doubled,
                    new_len,
                    "cap_check",
                )?,
                new_len,
                new_cap_doubled,
                "new_cap_realloc",
            )?
            .into_int_value();

        // Allocate new memory
        let element_size_const = self.context.i64_type().const_int(element_size, false);
        let alloc_size =
            self.builder
                .build_int_mul(new_cap_realloc, element_size_const, "alloc_size")?;

        let malloc_func = self
            .function_map
            .get("__malloc")
            .ok_or_else(|| CompilerError::internal("malloc function not found"))?;
        let malloc_call =
            self.builder
                .build_call(*malloc_func, &[alloc_size.into()], "malloc_call")?;
        let new_ptr_i8_realloc = malloc_call
            .try_as_basic_value()
            .left()
            .ok_or_else(|| CompilerError::internal("malloc call did not return a value"))?
            .into_pointer_value();

        let new_ptr_realloc = self
            .builder
            .build_bitcast(
                new_ptr_i8_realloc,
                element_llvm_type.ptr_type(AddressSpace::default()),
                "new_ptr_realloc",
            )?
            .into_pointer_value();

        // Copy old data using memcpy
        let copy_old_size =
            self.builder
                .build_int_mul(old_len, element_size_const, "copy_old_size")?;

        let memcpy_intrinsic = self
            .module
            .get_function("llvm.memcpy.p0.p0.i64")
            .unwrap_or_else(|| {
                let i8_ptr_type = self.context.i8_type().ptr_type(AddressSpace::default());
                let memcpy_type = self.context.void_type().fn_type(
                    &[
                        i8_ptr_type.into(),
                        i8_ptr_type.into(),
                        self.context.i64_type().into(),
                        self.context.bool_type().into(),
                    ],
                    false,
                );
                self.module
                    .add_function("llvm.memcpy.p0.p0.i64", memcpy_type, None)
            });

        let dest_i8_realloc = self
            .builder
            .build_bitcast(
                new_ptr_realloc,
                self.context.i8_type().ptr_type(AddressSpace::default()),
                "dest_i8_realloc",
            )?
            .into_pointer_value();

        let src_i8_old = self
            .builder
            .build_bitcast(
                old_ptr,
                self.context.i8_type().ptr_type(AddressSpace::default()),
                "src_i8_old",
            )?
            .into_pointer_value();

        self.builder.build_call(
            memcpy_intrinsic,
            &[
                dest_i8_realloc.into(),
                src_i8_old.into(),
                copy_old_size.into(),
                self.context.bool_type().const_int(0, false).into(),
            ],
            "memcpy_old",
        )?;

        // Free old memory
        let free_func = self
            .function_map
            .get("free")
            .ok_or_else(|| CompilerError::internal("free function not found"))?;
        self.builder
            .build_call(*free_func, &[src_i8_old.into()], "free_old_memory")?;

        self.builder.build_unconditional_branch(merge_block)?;

        // No realloc block: use existing array
        self.builder.position_at_end(no_realloc_block);
        let new_ptr_no_realloc = old_ptr;
        let new_cap_no_realloc = old_cap;
        self.builder.build_unconditional_branch(merge_block)?;

        // Merge block: phi nodes for ptr and cap
        self.builder.position_at_end(merge_block);

        let new_ptr_phi = self.builder.build_phi(
            element_llvm_type.ptr_type(AddressSpace::default()),
            "new_ptr",
        )?;
        new_ptr_phi.add_incoming(&[
            (&new_ptr_realloc, realloc_block),
            (&new_ptr_no_realloc, no_realloc_block),
        ]);
        let new_ptr = new_ptr_phi.as_basic_value().into_pointer_value();

        let new_cap_phi = self.builder.build_phi(self.context.i64_type(), "new_cap")?;
        new_cap_phi.add_incoming(&[
            (&new_cap_realloc, realloc_block),
            (&new_cap_no_realloc, no_realloc_block),
        ]);
        let new_cap = new_cap_phi.as_basic_value().into_int_value();

        // Copy source slice data to the end of the destination slice using memcpy
        let dest_offset_ptr = unsafe {
            self.builder
                .build_gep(element_llvm_type, new_ptr, &[old_len], "dest_offset_ptr")?
        };

        let copy_source_size =
            self.builder
                .build_int_mul(source_len, element_size_const, "copy_source_size")?;

        let dest_i8_offset = self
            .builder
            .build_bitcast(
                dest_offset_ptr,
                self.context.i8_type().ptr_type(AddressSpace::default()),
                "dest_i8_offset",
            )?
            .into_pointer_value();

        let src_i8_source = self
            .builder
            .build_bitcast(
                source_ptr,
                self.context.i8_type().ptr_type(AddressSpace::default()),
                "src_i8_source",
            )?
            .into_pointer_value();

        self.builder.build_call(
            memcpy_intrinsic,
            &[
                dest_i8_offset.into(),
                src_i8_source.into(),
                copy_source_size.into(),
                self.context.bool_type().const_int(0, false).into(),
            ],
            "memcpy_source",
        )?;

        // Create new slice struct with updated fields
        let new_slice_value = self
            .builder
            .build_insert_value(current_slice_struct, new_ptr, 0, "new_slice_with_ptr")?
            .into_struct_value();

        let new_slice_value = self
            .builder
            .build_insert_value(new_slice_value, new_len, 1, "new_slice_with_len")?
            .into_struct_value();

        let new_slice_value = self
            .builder
            .build_insert_value(new_slice_value, new_cap, 2, "new_slice_with_cap")?
            .into_struct_value();

        // Store the updated slice back to the variable
        self.builder.build_store(slice_ptr, new_slice_value)?;

        // Return void (i32 0 as placeholder)
        Ok(self.context.i32_type().const_int(0, false).into())
    }
}
